<template>
  <div class="marking" ref="mark">
    <svg v-if="!initFontWidth" :style="svgStyle" width="0" height="0" class="opacity">
      <text>
        <tspan ref="cnSpan">中文</tspan>
        <tspan ref="enSpan">En</tspan>
      </text>
    </svg>
    
    <svg
      v-else
      :style="svgStyle"
      :width="width"
      :height="height">
      <svg-defs :fill="relationFill" :lineFill="relationLineFill"/>
      <relation-mark
        v-for="i in visibleRelations"
        :key="'relation' + i.key"
        :disabled="disabled"
        :stroke="relationFill"
        :fontSize="labelFontSize"
        :fgColor="fill"
        :rowEnd="rowEnd"
        :rowStart="rowStart"
        :relation="i"
        :entityTypesMap="entityTypesMap"
      ></relation-mark>
      <relation-line :stroke="relationLineFill" :position="relationLinePosition"></relation-line>
      <text-mark
        v-for="(i, index) in marks"
        :key="'mark' + index"
        :disabled="disabled"
        :mark="i"
        :fill="markFill"
        :fontSize="fontSize"
        :labelFontSize="labelFontSize"
        :levelHeight="levelHeight"
        :entityTypesMap="entityTypesMap"
        :rowStart="rowStart"
        :rowEnd="rowEnd"
        :status="status"
      ></text-mark>
      <text-span
        v-for="(i, index) in tspans"
        :key="'span' + index"
        :tspan="i"
        @push-mark="pushMark"
      ></text-span>
    </svg>
    <!-- <entity-dialog
      :visible.sync="entityDialogVisible"
      :text="text"
      :relations="relations"
      :entityTypes="entityTypes"
      :entityId="entityId"
      :range="entityDialogRange"
      :entity="entityDialogEntity"
    ></entity-dialog>
    <relation-dialog
      :visible.sync="relationDialogVisible"
      :relationEntities="relationDialogEntity"
      :relationId="relationId"
      :relation="relationDialogRelation"
    ></relation-dialog> -->
  </div>
</template>

<script>
import SvgDefs from './components/svgDefs'
import TextSpan from './components/TextSpan'
import TextMark from './components/TextMark'
import RelationMark from './components/RelationMark'
import RelationLine from './components/RelationLine'
import EntityDialog from './components/dialog/EntityDialog'
import RelationDialog from './components/dialog/RelationDialog'
import { debounce } from 'link-utils'
import getTspans from './utils/getTspans'
import { getMarks } from './utils/getMarks'
import { formatRelations, getRelations } from './utils/getRelations'
import getLevel from './utils/getLevel'

export default {
  components: {
    TextSpan,
    TextMark,
    RelationMark,
    SvgDefs,
    EntityDialog,
    RelationLine,
    RelationDialog
  },

  provide () {
    return {
      handleEditEntity: this.handleEditEntity,
      handleLabelPosition: this.handleLabelPosition,
      emitEvent: this.emitEvent,
      setStatus: this.setStatus,
      setRelationLinePosition: this.setRelationLinePosition,
      setRelationDialogEntity: this.setRelationDialogEntity,
      getRelationDialogEntity: this.getRelationDialogEntity,
      testRelations: this.testRelations,
      handleEditRelation: this.handleEditRelation,
      setReselectId: this.setReselectId,
      getReselectId: this.getReselectId
    }
  },

  props: {
    text: String,
    entities: {
      type: Array,
      default: () => []
    },
    entityTypes: {
      type: Array,
      default: () => []
    },
    entityId: Number,
    relations: {
      type: Array,
      default: () => []
    },
    relationTypes: {
      type: Array,
      default: () => []
    },
    relationId: Number,
    disabled: Boolean
  },

  data () {
    return {
      initFontWidth: false,
      userSelect: true,
      fontWidth: 0,
      enFontScale: 0,
      width: 0,
      height: 0,

      xOffset: 14,
      yOffset: 8,
      // 正文字体大小
      fontSize: 14,
      // 标记字体大小
      labelFontSize: 12,
      // 正文行高
      lineHeight: 1.5,
      // 正文文本颜色
      fill: '#333',
      // 正文高亮处填充颜色
      markFill: 'rgba(8, 135, 255, 0.2)',
      // 每一层高度
      levelHeight: 30,
      // 关联高度
      relationHeight: 20,
      // 关联填充颜色
      relationFill: '#73bbff',
      // 设置关联时线的颜色
      relationLineFill: '#000',

      // relation key 保证实时更新
      relationKey: 0,
      relationMarks: [],
      tspans: [],
      marks: [],
      debounceResize: null,
      lastNum: 0,
      // 操作状态：set-relation / reselect-relation
      status: '',
      relationLinePosition: null,
      // dialog 部分信息
      entityDialogVisible: false,
      entityDialogRange: [0, 0],
      entityDialogEntity: null,
      relationDialogVisible: false,
      relationDialogEntity: [null, null],
      relationDialogRelation: null,
      reselectId: ''
    }
  },

  computed: {
    rowStart () {
      return this.xOffset
    },
    rowEnd () {
      return this.width !== 0
        ? this.width - this.xOffset
        : 0
    },
    svgStyle () {
      return {
        // 需要加 'px' 单位，否则在某些浏览器中无法解析
        fontSize: this.fontSize + 'px',
        fill: this.fill,
        userSelect: this.userSelect ? '' : 'none'
      }
    },
    entityTypesMap () {
      const map = {}
      this.entityTypes.forEach(i => {
        map[i.type] = i
      })
      return map
    },
    relationTypesMap () {
      const map = {}
      this.relationTypes.forEach(i => {
        map[i.type] = i
      })
      return map
    },
    formatedRelations () {
      return formatRelations(this.relations, this.relationTypesMap, this.entities)
    },
    visibleRelations () {
      const relations = []
      this.relationMarks.forEach(i => {
        const { id, position, mode, y, level, leftPosition, rightPosition, from, left, text, type, fromEntity, toEntity } = i
        const line = y - this.fontSize - (level - 1) * this.levelHeight - this.relationHeight
        // 判断箭头方向
        const markerEnd = from === left
        const relation = {
          key: this.relationKey,
          id,
          position,
          mode,
          markerEnd,
          text,
          type,
          fromEntity,
          toEntity
        }
        // 横跨直接渲染
        if (position === 'center') {
          relations.push({
            ...relation,
            x1: this.rowStart,
            x2: this.rowEnd,
            y1: line,
            y2: line
          })
        } else if (position === 'left' && leftPosition) {
          relations.push({
            ...relation,
            x1: mode === 'in' ? leftPosition.x + leftPosition.width : leftPosition.x,
            x2: this.rowEnd,
            y1: leftPosition.y,
            y2: line
          })
        } else if (position === 'right' && rightPosition) {
          relations.push({
            ...relation,
            x1: this.rowStart,
            x2: mode === 'in' ? rightPosition.x : rightPosition.x + rightPosition.width,
            y1: rightPosition.y,
            y2: line
          })
        } else if (position === 'complete' && leftPosition && rightPosition) {
          relations.push({
            ...relation,
            x1: mode === 'in' ? leftPosition.x + leftPosition.width : leftPosition.x,
            x2: mode === 'in' ? rightPosition.x : rightPosition.x + rightPosition.width,
            y1: leftPosition.y,
            y2: rightPosition.y,
            line
          })
        }
        this.relationKey++
      })
      return relations
    }
  },

  watch: {
    entities () {
      this.renderText(true)
    },
    relations () {
      this.renderText(true)
    }
  },

  methods: {
    handleMouseenter () {
      this.userSelect = true
    },
    handleMouseleave () {
      this.userSelect = false
    },
    handleMousemove () {
      if (this.userSelect) return
      this.userSelect = true
    },
    renderText (force = false) {
      const width = this.$refs.mark.clientWidth
      // 左右各留 xOffset
      const num = Math.floor((width - 2 * this.xOffset) / this.fontWidth)
      // 每一行字数不变不需要进行更新
      if (num === this.lastNum && !force) {
        return
      } else {
        this.lastNum = num
      }

      const relationMarks = []
      const tspans = getTspans(this.text, num, this.enFontScale)
      // 默认 y = 0 是 tspan 基线在 0 坐标上
      let y = this.yOffset + this.fontSize
      tspans.forEach((i, index) => {
        const marks = getMarks(i, index + 1, this.entities, this.entityTypesMap)

        const relations = getRelations(i, this.formatedRelations)

        const scale = +(this.labelFontSize / this.fontSize).toFixed(3)
        // 左侧有可能有多余空间
        const left = -Math.floor(this.xOffset / this.fontSize)
        const right = num
        // getMarksLevel 会修改 marks 和 relations 的层级信息
        // const level = 0
        const level = getLevel(marks, relations, i, this.text, scale, left, right)

        i.relations = relations
        i.marks = marks
        i.x = this.xOffset
        i.y = this.getYOffset(index, level > 0 ? level -1 : 0, y)
        y = i.y

        if (relations) {
          relations.forEach(i => {
            i.y = y
          })
          relationMarks.push(...relations)
        }
      })

      this.relationMarks = relationMarks
      // 重新渲染 tspan 前需要清空所有 mark
      this.marks = []
      this.tspans = tspans

      this.width = width
      // svg 的高度等于最后一行 y 位置再加一半行高
      this.height = this.tspans[this.tspans.length - 1].y + this.lineHeight * this.fontSize / 2
    },
    getYOffset(index, level, prevY) {
      let y = prevY
      if (index !== 0) {
        y += this.fontSize * this.lineHeight
      }
      y += level * this.levelHeight
      return y
    },
    handleResize () {
      this.renderText()
    },
    pushMark (mark) {
      this.marks.push(mark)
      // 判断中线位置
      this.marks.forEach(i => {
        if (i.position === 'complete') return
        let length = 0
        let sx = 0
        this.marks.forEach(j => {
          if (j.id === i.id) {
            length += j.width
            // 获取 sx
            if (j !== i && j.row < i.row) {
              sx += j.width
            }
          }
        })
        // TODO: 此处有可能会更新 cx 不生效
        i.cx = i.x + length / 2 - sx
      })
    },
    handleLabelPosition (position) {
      this.relationMarks.forEach((i, index) => {
        if (i.left === position.id) {
          i.leftPosition = position
        }
        if (i.right === position.id) {
          i.rightPosition = position
        }
        if (i.position) {
          this.$set(this.relationMarks, index, i)
        }
      })
    },
    handleSelect () {
      if (this.disabled) return

      const selection = window.getSelection()
      if (selection.isCollapsed) return
      const anchor = selection.anchorNode.parentElement
      const focus = selection.focusNode.parentElement
      const anchorStart = anchor.dataset && anchor.dataset.spanStart
      const focusStart = focus.dataset && focus.dataset.spanStart
      // spanStart 为字符串
      if (anchorStart && focusStart) {
        const range1 = Number(anchorStart) + selection.anchorOffset
        const range2 = Number(focusStart) + selection.focusOffset
        this.entityDialogRange = range1 < range2
          ? [range1, range2 - 1]
          : [range2, range1 - 1]
        selection.removeAllRanges()
        this.entityDialogEntity = null
        this.entityDialogVisible = true
      }
    },
    handleEditEntity (id) {
      this.entityDialogEntity = this.entities.find(i => {
        return i[0] === id
      })
      this.entityDialogVisible = true
    },
    setStatus (status) {
      this.status = status
    },
    setRelationLinePosition (position) {
      this.relationLinePosition = position
    },
    setRelationDialogEntity (type, entity) {
      if (type === 'from') {
        this.relationDialogEntity.splice(0, 1, entity)
      }
      if (type === 'to') {
        this.relationDialogEntity.splice(1, 1, entity)
        this.relationDialogRelation = null
        this.relationDialogVisible = true
      }
    },
    getRelationDialogEntity () {
      return this.relationDialogEntity
    },
    // 返回传入实体类型可能存在的关系
    testRelations(fromType, toType) {
      return this.relationTypes.filter(i => {
        return i.args[0].targets.includes(fromType) && i.args[1].targets.includes(toType)
      })
    },
    handleEditRelation (relation) {
      this.relationDialogRelation = relation
      this.relationDialogVisible = true
    },
    setReselectId (id) {
      this.reselectId = id
    },
    getReselectId () {
      return this.reselectId
    },
    emitEvent (event, ...arg) {
      this.$emit(event, ...arg)
    }
  },

  mounted () {
    const cnFontWidth = this.$refs.cnSpan.getBoundingClientRect().width / 2
    const enFontWidth = this.$refs.enSpan.getBoundingClientRect().width / 2
    this.fontWidth = cnFontWidth > this.fontSize ? Number(cnFontWidth.toFixed(2)) : this.fontSize
    this.enFontScale = Number((enFontWidth / cnFontWidth).toFixed(2))
    this.initFontWidth = true
    this.$nextTick(() => {
      this.renderText()
      this.debounceResize = debounce(this.handleResize, 200)
      window.addEventListener('resize', this.debounceResize)
    })
  },

  beforeDestroy () {
    console.log('beforeDestroy');
    window.removeEventListener('resize', this.debounceResize)
  }
}
</script>

<style lang="stylus">
$focusColor= #000;
$reselectColor= #F82418;

.marking {
  // margin: 10px;

  svg {
    /* 字体必须为等宽字体，字间距设置为 0 */
    font-family: serif;
    letter-spacing: 0px;

    path {
      stroke-width: 1;
      fill: none;
      fill-opacity: 0;
    }
  }

  tspan {
    /* 保留所有空格 */
    white-space: break-spaces;
    outline: none;
  }

  .opacity {
    opacity: 0;
  }

  .text-label, .text-relation tspan {
    cursor: pointer;
  }

  .text-label {
    rect {
      opacity: 0.25;
    }
  }

  /* 聚焦关联时的样式 */
  .text-relation.focus {
    path {
      stroke: $focusColor;
    }
    .marker-start {
      marker-start: url(#triangle-start-line)
    }
    .marker-end {
      marker-end: url(#triangle-end-line)
    }
    text tspan {
      fill: $focusColor;
      font-weight: bold;
    }
  }
  .text-label.focus {
    outline-width: 2px!important;
  }
  /* 重设关联时的样式 */
  .text-relation.reselect {
    path {
      stroke: $reselectColor;
    }
    .marker-start {
      marker-start: url(#triangle-start-reselect)
    }
    .marker-end {
      marker-end: url(#triangle-end-reselect)
    }
    text tspan {
      fill: $reselectColor;
      font-weight: bold;
    }
  }
  .text-label.reselect {
    outline: 2px solid $reselectColor!important;
  }
}

.marking-tooltip {
  font-size: 12px;
  & > div:first-child {
    display: flex;
    justify-content: space-between;
    border-bottom: 1px solid #fff;
    span + span {
      margin-left: 10px;
    }
  }
}
</style>